{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "节点 Node\n",
    "关系 Relationship"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(alice:Person {name:\"Alice\"}) (bob:Person {name:\"Bob\"}) (alice)-[:KNOWS]->(bob)\n"
     ]
    }
   ],
   "source": [
    "from py2neo import Node, Relationship,size ,order\n",
    "#创建节点\n",
    "a = Node('Person', name='Alice')\n",
    "b = Node('Person', name='Bob')\n",
    "r = Relationship(a, 'KNOWS', b)\n",
    "print(a, b, r)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(alice:Person {age:20,name:\"Alice\"}) (bob:Person {age:21,name:\"Bob\"}) (alice)-[:KNOWS {time:\"2017/08/31\"}]->(bob)\n"
     ]
    }
   ],
   "source": [
    "#增加节点属性\n",
    "a['age'] = 20\n",
    "b['age'] = 21\n",
    "r['time'] = '2017/08/31'\n",
    "print(a, b, r)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(alice:Person {age:20,location:\"北京\",name:\"Alice\"})\n"
     ]
    }
   ],
   "source": [
    "#设置属性的默认值\n",
    "a.setdefault('location', '北京')\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(alice:Person {age:20,location:\"上海\",name:\"Alice\"})\n"
     ]
    }
   ],
   "source": [
    "#如果该属性原来存在，则setdefault不会覆盖该属性值\n",
    "a['location'] = '上海'\n",
    "a.setdefault('location', '北京')\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(alice:Person {age:21,location:\"上海\",name:\"Amy\"})\n"
     ]
    }
   ],
   "source": [
    "#可以通过字典更新属性值\n",
    "data = {\n",
    "    'name': 'Amy',\n",
    "    'age': 21\n",
    "}\n",
    "a.update(data)\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "({(alice:Person {name:\"Alice\"}), (bob:Person {name:\"Bob\"})}, {(alice)-[:KNOWS]->(bob)})\n"
     ]
    }
   ],
   "source": [
    "#子图，是 Node 和 Relationship 的集合，最简单的构造子图的方式是通过关系运算符\n",
    "a = Node('Person', name='Alice')\n",
    "b = Node('Person', name='Bob')\n",
    "r = Relationship(a, 'KNOWS', b)\n",
    "s = a | b | r #s是一个子图\n",
    "print(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "frozenset({(alice:Person {name:\"Alice\"}), (bob:Person {name:\"Bob\"})})\n",
      "frozenset({(alice)-[:KNOWS]->(bob)})\n"
     ]
    }
   ],
   "source": [
    "#另外还可以通过 nodes() 和 relationships() 方法获取所有的 Node 和 Relationship\n",
    "print(s.nodes())\n",
    "print(s.relationships())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "({(alice:Person {name:\"Alice\"}), (bob:Person {name:\"Bob\"})}, {})\n"
     ]
    }
   ],
   "source": [
    "#还可以利用 & 取 Subgraph 的交集\n",
    "s1 = a | b | r\n",
    "s2 = a | b\n",
    "print(s1 & s2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "frozenset({'name'})\n",
      "frozenset({'Person'})\n",
      "frozenset({(alice:Person {name:\"Alice\"}), (bob:Person {name:\"Bob\"})})\n",
      "frozenset({(alice)-[:KNOWS]->(bob)})\n",
      "frozenset({'KNOWS'})\n"
     ]
    }
   ],
   "source": [
    "#还可以分别利用 keys()、labels()、nodes()、relationships()、types() 分别获取 Subgraph 的 Key、Label、Node、Relationship、Relationship Type\n",
    "s = a | b | r\n",
    "print(s.keys())\n",
    "print(s.labels())\n",
    "print(s.nodes())\n",
    "print(s.relationships())\n",
    "print(s.types())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2\n",
      "1\n"
     ]
    }
   ],
   "source": [
    "#还可以用 order() 或 size() 方法来获取 Subgraph 的 Node 数量和 Relationship 数量\n",
    "s = a | b | r\n",
    "print(order(s))  #节点数量\n",
    "print(size(s))   #关系数量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(alice)-[:KNOWS]->(bob)-[:LIKES]->(mike)<-[:KNOWS]-(alice)\n"
     ]
    }
   ],
   "source": [
    "#Walkable 是增加了遍历信息的 Subgraph，我们通过 + 号便可以构建一个 Walkable 对象\n",
    "a = Node('Person', name='Alice')\n",
    "b = Node('Person', name='Bob')\n",
    "c = Node('Person', name='Mike')\n",
    "ab = Relationship(a, \"KNOWS\", b)\n",
    "ac = Relationship(a, \"KNOWS\", c)\n",
    "w = ab + Relationship(b, \"LIKES\", c) + ac\n",
    "print(w)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(alice:Person {name:\"Alice\"})\n",
      "(alice)-[:KNOWS]->(bob)\n",
      "(bob:Person {name:\"Bob\"})\n",
      "(bob)-[:LIKES]->(mike)\n",
      "(mike:Person {name:\"Mike\"})\n",
      "(alice)-[:KNOWS]->(mike)\n",
      "(alice:Person {name:\"Alice\"})\n"
     ]
    }
   ],
   "source": [
    "#可以调用 walk() 方法实现遍历\n",
    "from py2neo import walk\n",
    "for item in walk(w):\n",
    "    print(item)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(alice:Person {name:\"Alice\"})\n",
      "(alice:Person {name:\"Alice\"})\n",
      "((alice:Person {name:\"Alice\"}), (bob:Person {name:\"Bob\"}), (mike:Person {name:\"Mike\"}), (alice:Person {name:\"Alice\"}))\n",
      "((alice)-[:KNOWS]->(bob), (bob)-[:LIKES]->(mike), (alice)-[:KNOWS]->(mike))\n"
     ]
    }
   ],
   "source": [
    "#可以利用 start_node()、end_node()、nodes()、relationships() 方法来获取起始 Node、终止 Node、所有 Node 和 Relationship\n",
    "print(w.start_node())\n",
    "print(w.end_node())\n",
    "print(w.nodes())\n",
    "print(w.relationships())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在 database 模块中包含了和 Neo4j 数据交互的 API，最重要的当属 Graph，它代表了 Neo4j 的图数据库，同时 Graph 也提供了许多方法来操作 Neo4j 数据库。\n",
    "Graph 在初始化的时候需要传入连接的 URI，初始化参数有 bolt、secure、host、http_port、https_port、bolt_port、user、password，\n",
    "详情说明可以参考：http://py2neo.org/v3/database.html#py2neo.database.Graph。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "#Graph()实例化\n",
    "from py2neo import Graph\n",
    "graph = Graph(\"http://localhost:7474\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#可以利用 create() 方法传入 Subgraph 对象来将关系图添加到数据库中\n",
    "a = Node('Person', name='Alice')\n",
    "b = Node('Person', name='Bob')\n",
    "r = Relationship(a, 'KNOWS', b)\n",
    "s = a | b | r\n",
    "graph.create(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#可以单独添加Node或Relationship\n",
    "a = Node('Person', name='Mike')\n",
    "graph.create(a)\n",
    "b = Node('Person', name='Jack')\n",
    "ab = Relationship(a, 'KNOWS', b)\n",
    "graph.create(ab)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[{'p.name': 'Alice'}, {'p.name': 'Bob'}, {'p.name': 'Mike'}, {'p.name': 'Jack'}]\n"
     ]
    }
   ],
   "source": [
    "#可以利用 data() 方法来获取查询结果\n",
    "from py2neo import Graph\n",
    "data = graph.data('MATCH (p:Person) return p.name ') #通过 CQL 语句实现的查询\n",
    "print(data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  p.name\n",
      "0  Alice\n",
      "1    Bob\n",
      "2   Mike\n",
      "3   Jack\n"
     ]
    }
   ],
   "source": [
    "#转换为DataFrame\n",
    "from pandas import DataFrame\n",
    "df = DataFrame(data)\n",
    "print(df)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "a = Node('Person', name='Alice', age=21, location='广州')\n",
    "b = Node('Person', name='Bob', age=22, location='上海')\n",
    "c = Node('Person', name='Mike', age=21, location='北京')\n",
    "r1 = Relationship(a, 'KNOWS', b)\n",
    "r2 = Relationship(b, 'KNOWS', c)\n",
    "graph.create(a)\n",
    "graph.create(r1)\n",
    "graph.create(r2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(mike:Person {age:21,location:\"北京\",name:\"Mike\"}), (alice:Person {age:21,location:\"广州\",name:\"Alice\"})]\n"
     ]
    }
   ],
   "source": [
    "from py2neo import Graph, NodeSelector\n",
    "#用 NodeSelector 来筛选 age 为 21 的 Person Node\n",
    "selector = NodeSelector(graph)\n",
    "persons = selector.select('Person', age=21)\n",
    "print(list(persons))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(alice:Person {name:\"Alice\"}), (alice:Person {age:21,location:\"广州\",name:\"Alice\"})]\n"
     ]
    }
   ],
   "source": [
    "#可以使用 where() 进行更复杂的查询，例如查找 name 是 A 开头的 Person Node\n",
    "persons = selector.select('Person').where('_.name =~ \"A.*\"')\n",
    "print(list(persons))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(alice)-[:KNOWS]->(bob)\n"
     ]
    }
   ],
   "source": [
    "#可以利用 match() 或 match_one() 方法对 Relationship 进行查找\n",
    "relationship = graph.match_one(rel_type='KNOWS')\n",
    "print(relationship)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(mike:Person {age:21,location:\"北京\",name:\"Mike\"}), (alice:Person {age:21,location:\"广州\",name:\"Alice\"}), (bob:Person {age:22,location:\"上海\",name:\"Bob\"}), (alice:Person {name:\"Alice\"}), (bob:Person {name:\"Bob\"}), (mike:Person {name:\"Mike\"}), (jack:Person {name:\"Jack\"})]\n"
     ]
    }
   ],
   "source": [
    "#可以使用 order_by() 进行排序\n",
    "persons = selector.select('Person').order_by('_.age')\n",
    "print(list(persons))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(alice:Person {name:\"Alice\"})\n"
     ]
    }
   ],
   "source": [
    "#如果要查询单个节点的话，可以使用 first() 方法\n",
    "person = selector.select('Person').where('_.name =~ \"A.*\"').first()\n",
    "print(person)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('p': (alice:Person {name:\"Alice\"})), ('p': (bob:Person {name:\"Bob\"})), ('p': (bob:Person {age:22,location:\"上海\",name:\"Bob\"})), ('p': (mike:Person {age:21,location:\"北京\",name:\"Mike\"})), ('p': (mike:Person {name:\"Mike\"}))]\n"
     ]
    }
   ],
   "source": [
    "#可以通过 run() 方法直接执行 CQL 语句\n",
    "data = graph.run('MATCH (p:Person) RETURN p LIMIT 5')\n",
    "print(list(data))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "OGM 类似于 ORM，意为 Object Graph Mapping，这样可以实现一个对象和 Node 的关联"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<Person name='Mike'>\n",
      "Mike\n",
      "21\n"
     ]
    }
   ],
   "source": [
    "#可以用它来结合 Graph 查询\n",
    "from py2neo import Graph\n",
    "from py2neo.ogm import GraphObject, Property\n",
    "\n",
    "class Person(GraphObject):\n",
    "    __primarykey__ = 'name'\n",
    "\n",
    "    name = Property()\n",
    "    age = Property()\n",
    "    location = Property()\n",
    "\n",
    "person = Person.select(graph).where(age=21).first()\n",
    "print(person)\n",
    "print(person.name)\n",
    "print(person.age)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(mike:Person {age:21,location:\"北京\",name:\"Mike\"})\n",
      "(mike:Person {age:22,location:\"北京\",name:\"Mike\"})\n"
     ]
    }
   ],
   "source": [
    "#可以用它动态改变 Node 的属性，例如修改某个 Node 的 age 属性\n",
    "person = Person.select(graph).where(age=21).first()\n",
    "print(person.__ogm__.node)\n",
    "person.age = 22\n",
    "print(person.__ogm__.node)\n",
    "graph.push(person)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[<Person name='Bob'>]\n",
      "[<Person name='Bob'>, <Person name='Durant'>]\n"
     ]
    }
   ],
   "source": [
    "from py2neo.ogm import GraphObject, Property, RelatedTo\n",
    "\n",
    "#也可以通过映射关系进行 Relationship 的调整，例如通过 Relationship 添加一个关联 Node\n",
    "\n",
    "class Person(GraphObject):\n",
    "    __primarykey__ = 'name'\n",
    "\n",
    "    name = Property()\n",
    "    age = Property()\n",
    "    location = Property()\n",
    "    knows = RelatedTo('Person', 'KNOWS')\n",
    "\n",
    "person = Person.select(graph).where(age=21).first()\n",
    "print(list(person.knows))\n",
    "new_person = Person()\n",
    "new_person.name = 'Durant'\n",
    "new_person.age = 28\n",
    "person.knows.add(new_person)\n",
    "print(list(person.knows))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这样我们就完成了 Node 和 Relationship 的添加，同时由于设置了 primarykey 为 name，所以不会重复添加。\n",
    "但是注意此时数据库并没有更新，只是对象更新了，如果要更新到数据库中还需要调用 Graph 对象的 push() 或 pull() 方法，添加如下代码即可："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "graph.push(person)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[<Person name='Bob'>, <Person name='Durant'>]\n",
      "[<Person name='Bob'>]\n"
     ]
    }
   ],
   "source": [
    "target=Person.select(graph).where(name='Durant').first()\n",
    "print(list(person.knows))\n",
    "person.knows.remove(target)  #可以通过remove来删除节点间的关系\n",
    "print(list(person.knows))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "graph.push(person)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "graph.delete(target) #先删除节点上的关系，才能删除节点"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
